/* @file misc.h
*/

/*
 *    Copyright 2009 10gen Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#pragma once

#include <ctime>
#include <limits>
#include <string>
#include <Windows.h>
#include "mongo/util/assert_util.h"


#define DAYSPERWEEK 7
typedef double DOUBLE;
const DOUBLE VT_SECOND_1970 = 2209161600.0;
const DOUBLE SECONDS_IN_ONE_DAY = 86400;

inline BOOL IsLeapYear(WORD nYear)
{
    return !(nYear % 4) && (nYear % 100) || !(nYear % 400);
}

inline int DaysFromJan1st(WORD wYear, WORD wMonth)
{
    static WORD monthday[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
    return monthday[wMonth - 1] + (IsLeapYear(wYear) && wMonth > 2 ? 1 : 0);
}
inline tm* GetTmByVariant(DOUBLE dTime)
{
    SYSTEMTIME stResult;
    VariantTimeToSystemTime(dTime, &stResult);
    static tm s_tm;
    s_tm.tm_sec = stResult.wSecond;
    s_tm.tm_min = stResult.wMinute;
    s_tm.tm_hour = stResult.wHour;
    s_tm.tm_mday = stResult.wDay;
    s_tm.tm_mon = stResult.wMonth-1;
    s_tm.tm_year = stResult.wYear-1900;
    s_tm.tm_wday = stResult.wDayOfWeek;
    s_tm.tm_yday = DaysFromJan1st(stResult.wYear, stResult.wMonth);
    s_tm.tm_isdst = 0;
    return &s_tm;
}
inline void strfmt_ce(char *str, const char *fmt, ...)
{
    int ival, ilen;
    char *sval;
    static int pow[5] = { 1, 10, 100, 1000, 10000 };
    va_list vp;

    va_start(vp, fmt);
    while (*fmt)
    {
        if (*fmt++ == '%')
        {
            ilen = *fmt++ - '0';
            if (ilen == 0)                // zero means string arg
            {
                sval = va_arg(vp, char*);
                while (*sval)
                    *str++ = *sval++;
            }
            else                          // always leading zeros
            {
                ival = va_arg(vp, int);
                while (ilen)
                {
                    ival %= pow[ilen--];
                    *str++ = (char)('0' + ival / pow[ilen]);
                }
            }
        }
        else  *str++ = fmt[-1];
    }
    *str = '\0';
    va_end(vp);
}
inline size_t strftime_ce(char* pTimeStr, size_t stStrLen, const char* pFmt, const tm* pTM)
{
    static char *aday[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    static char *day[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
    static char *amonth[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    static char *month[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};

    int            w;
    char        *p, *q, *r;
    static char    buf[26];

    p = pTimeStr;
    q = pTimeStr + stStrLen - 1;
    while ((*pFmt != '\0'))
    {
        if (*pFmt++ == '%')
        {
            r = buf;
            switch (*pFmt++)
            {
            case '%' :
                r = "%";
                break;

            case 'a' :
                r = aday[pTM->tm_wday];
                break;

            case 'A' :
                r = day[pTM->tm_wday];
                break;

            case 'b' :
                r = amonth[pTM->tm_mon];
                break;

            case 'B' :
                r = month[pTM->tm_mon];
                break;

            case 'c' :
                strfmt_ce(r, "%0 %0 %2 %2:%2:%2 %4",
                    aday[pTM->tm_wday], amonth[pTM->tm_mon],
                    pTM->tm_mday,pTM->tm_hour, pTM->tm_min,
                    pTM->tm_sec, pTM->tm_year+1900);
                break;

            case 'd' :
                strfmt_ce(r,"%2",pTM->tm_mday);
                break;

            case 'H' :
                strfmt_ce(r,"%2",pTM->tm_hour);
                break;

            case 'I' :
                strfmt_ce(r,"%2",(pTM->tm_hour%12)?pTM->tm_hour%12:12);
                break;

            case 'j' :
                strfmt_ce(r,"%3",pTM->tm_yday+1);
                break;

            case 'm' :
                strfmt_ce(r,"%2",pTM->tm_mon+1);
                break;

            case 'M' :
                strfmt_ce(r,"%2",pTM->tm_min);
                break;

            case 'p' :
                r = (pTM->tm_hour>11)?"PM":"AM";
                break;

            case 'S' :
                strfmt_ce(r,"%2",pTM->tm_sec);
                break;

            case 'U' :
                w = pTM->tm_yday/7;
                if (pTM->tm_yday%7 > pTM->tm_wday)
                    w++;
                strfmt_ce(r, "%2", w);
                break;

            case 'W' :
                w = (pTM->tm_yday + DAYSPERWEEK -
                    (pTM->tm_wday ?
                    (pTM->tm_wday - 1) :
                (DAYSPERWEEK - 1))) / DAYSPERWEEK;
                strfmt_ce(r, "%2", w);
                break;

            case 'w' :
                strfmt_ce(r,"%1",pTM->tm_wday);
                break;

            case 'x' :
                strfmt_ce(r, "%2/%2/%2", pTM->tm_mon + 1,
                    pTM->tm_mday, pTM->tm_year+1900);
                break;

            case 'X' :
                strfmt_ce(r, "%2:%2:%2", pTM->tm_hour,
                    pTM->tm_min, pTM->tm_sec);
                break;

            case 'y' :
                strfmt_ce(r,"%2",pTM->tm_year%100);
                break;

            case 'Y' :
                strfmt_ce(r,"%4",pTM->tm_year+1900);
                break;

            case 'Z' :
                //                r = (pTM->tm_isdst && tz_name[1][0])?tz_name[1]:tz_name[0];
                break;

            default:
                buf[0] = '%';        // reconstruct the format
                buf[1] = pFmt[-1];
                buf[2] = '\0';
                if (buf[1] == 0)
                    pFmt--;            // back up if at end of string
            }
            while (*r)
            {
                if (p == q)
                {
                    *q = '\0';
                    return 0;
                }
                *p++ = *r++;
            }
        }
        else
        {
            if (p == q)
            {
                *q = '\0';
                return 0;
            }
            *p++ = pFmt[-1];
        }
    }
    *p = '\0';
    return p - pTimeStr;
}
inline tm* localtime_ce(const time_t* pTT)
{
    TIME_ZONE_INFORMATION tz;
    GetTimeZoneInformation(&tz);
    return GetTmByVariant((VT_SECOND_1970 + (*pTT) - tz.Bias*60)/SECONDS_IN_ONE_DAY);
}

inline void ctime_s(char *_Buffer, size_t _SizeInBytes, const time_t * _Time)
{
    struct tm* pToday = NULL;
    pToday = localtime_ce(_Time);
    strftime_ce(_Buffer, _SizeInBytes, "%Y-%m-%d %H:%M:%S", pToday);
}

inline void gmtime_s(struct tm * _Tm, const time_t * _Time)
{
    *_Tm = *localtime_ce(_Time);
}

namespace mongo {

    inline void time_t_to_String(time_t t, char *buf) {
#if defined(_WIN32)
        ctime_s(buf, 32, &t);
#else
        ctime_r(&t, buf);
#endif
        buf[24] = 0; // don't want the \n
    }

    inline std::string time_t_to_String(time_t t = time(0) ) {
        char buf[64];
#if defined(_WIN32)
        ctime_s(buf, sizeof(buf), &t);
#else
        ctime_r(&t, buf);
#endif
        buf[24] = 0; // don't want the \n
        return buf;
    }

    inline std::string time_t_to_String_no_year(time_t t) {
        char buf[64];
#if defined(_WIN32)
        ctime_s(buf, sizeof(buf), &t);
#else
        ctime_r(&t, buf);
#endif
        buf[19] = 0;
        return buf;
    }

    inline std::string time_t_to_String_short(time_t t) {
        char buf[64];
#if defined(_WIN32)
        ctime_s(buf, sizeof(buf), &t);
#else
        ctime_r(&t, buf);
#endif
        buf[19] = 0;
        if( buf[0] && buf[1] && buf[2] && buf[3] )
            return buf + 4; // skip day of week
        return buf;
    }

    struct Date_t {
        // TODO: make signed (and look for related TODO's)
        unsigned long long millis;
        Date_t(): millis(0) {}
        Date_t(unsigned long long m): millis(m) {}
        operator unsigned long long&() { return millis; }
        operator const unsigned long long&() const { return millis; }
        void toTm (tm *buf) {
            time_t dtime = toTimeT();
#if defined(_WIN32)
            gmtime_s(buf, &dtime);
#else
            gmtime_r(&dtime, buf);
#endif
        }
        std::string toString() const {
            char buf[64];
            time_t_to_String(toTimeT(), buf);
            return buf;
        }
        time_t toTimeT() const {
            // cant use uassert from bson/util
            verify((long long)millis >= 0); // TODO when millis is signed, delete 
            verify(((long long)millis/1000) < (std::numeric_limits<time_t>::max)());
            return (time_t)millis / 1000;
        }
    };

    // Like strlen, but only scans up to n bytes.
    // Returns -1 if no '0' found.
    inline int strnlen( const char *s, int n ) {
        for( int i = 0; i < n; ++i )
            if ( !s[ i ] )
                return i;
        return -1;
    }

    inline bool isNumber( char c ) {
        return c >= '0' && c <= '9';
    }

    inline unsigned stringToNum(const char *str) {
        unsigned x = 0;
        const char *p = str;
        while( 1 ) {
            if( !isNumber(*p) ) {
                if( *p == 0 && p != str )
                    break;
                throw 0;
            }
            x = x * 10 + *p++ - '0';
        }
        return x;
    }

}
